/*
 * Copyright (C) 2017 Samuel Wall
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.samuelwall.materialtaptargetprompt.extras.focals;

import ohos.agp.components.Component;
import ohos.agp.render.BlendMode;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptFocal;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptOptions;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptUtils;
import uk.co.samuelwall.materialtaptargetprompt.utils.Constants;

/**
 * Prompt focal implementation to draw the focal as a circle.
 *
 * @since 2021-06-17
 */
public class CirclePromptFocal extends PromptFocal {
    Paint mPaint;
    int mRippleAlpha;
    float mRadius;
    float mBaseRadius;
    float mRippleRadius;
    int mBaseAlpha;
    Point mPosition;
    RectFloat mBounds;
    Path mPathOut;
    Path mPathIn;

    /**
     * Constructor.
     */
    public CirclePromptFocal() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPosition = new Point();
        mBounds = new RectFloat();
        mPathOut = new Path();
        mPathIn = new Path();
    }

    /**
     * Set the focal radius.
     *
     * @param radius The radius for the circle to be drawn.
     * @return This prompt focal
     */

    public CirclePromptFocal setRadius(final float radius) {
        mBaseRadius = radius;
        return this;
    }

    @Override
    public RectFloat getBounds() {
        return mBounds;
    }

    @Override
    public Path getPath() {
        return mPathOut;
    }

    @Override
    public Point calculateAngleEdgePoint(final float angle, final float padding) {
        // Calculate the x and y on the focal from the angle calculated
        final float focalRadius = mBounds.getWidth() + padding;
        final float xx = calculateX(angle, focalRadius, mBounds.getCenter().getPointX());
        final float yy = calculateY(angle, focalRadius, mBounds.getCenter().getPointY());
        return new Point(xx, yy);
    }

    /**
     * Calculates the x position on a circle for an angle and centre point x.
     *
     * @param angle   The angle on the circle to get the x position for.
     * @param radius  The circle radius.
     * @param centreX The centre x position for the circle.
     * @return The calculated x position for the angle.
     */
    private float calculateX(final float angle, final float radius, final float centreX) {
        return centreX + radius * (float) Math.cos(Math.toRadians(angle));
    }

    /**
     * Calculates the y position on a circle for an angle and centre point y.
     *
     * @param angle   The angle on the circle to get the y position for.
     * @param radius  The circle radius.
     * @param centreY The centre y position for the circle.
     * @return The calculated y position for the angle.
     */
    private float calculateY(final float angle, final float radius, final float centreY) {
        return centreY + radius * (float) Math.sin(Math.toRadians(angle));
    }

    @Override
    public void setColour(int colour) {
        mPaint.setColor(new Color(colour));
        mBaseAlpha = Color.alpha(colour);
        mPaint.setAlpha(mBaseAlpha);
    }

    @Override
    public void prepare(PromptOptions options, final int[] promptViewPosition) {
        Component target = options.getTargetView();

        if (target != null) {
            final int[] targetPosition = target.getLocationOnScreen();
            if (targetPosition != null) {
                prepare(options, targetPosition[0] - promptViewPosition[0] + (target.getWidth() / Constants.NUM_2F),
                        targetPosition[1] - promptViewPosition[1] + (target.getHeight() / Constants.NUM_2F));
            }
        }

    }

    @Override
    public void prepare(PromptOptions options, float targetX, float targetY) {
        mPromptOptions = options;
        mPosition = new Point(targetX, targetY);
        mBounds.left = targetX - mBaseRadius;
        mBounds.top = targetY - mBaseRadius;
        mBounds.right = targetX + mBaseRadius;
        mBounds.bottom = targetY + mBaseRadius;
    }

    @Override
    public void update(float revealModifier, float alphaModifier) {
        mPaint.setAlpha(alphaModifier * mBaseAlpha / Constants.NUM_255);
        if (mPromptOptions.getTargetView() != null) {
            if (mPromptOptions.getIconDrawable() == null) {
                mPathIn = new Path();
                Component targetView = mPromptOptions.getTargetView();
                if (targetView != null) {
                    float radius = targetView.getWidth() / Constants.NUM_2F;
                    int width = targetView.getWidth();
                    int height = targetView.getHeight();
                    if (width != height) {
                        radius = (float) Math.sqrt(
                                Math.pow(width / Constants.NUM_2F, Constants.NUM_2)
                                        + Math.pow(height / Constants.NUM_2F, Constants.NUM_2));
                    }

                    mPathIn.addCircle(mPosition.getPointX(), mPosition.getPointY(),
                            radius, Path.Direction.CLOCK_WISE);
                }
            }
        }
        mRadius = mBaseRadius * revealModifier;
        mPathOut = new Path();
        mPathOut.addCircle(mPosition.getPointX(), mPosition.getPointY(), mRadius, Path.Direction.CLOCK_WISE);
    }

    @Override
    public void updateRipple(float revealModifier, float alphaModifier) {
        mRippleRadius = mBaseRadius * revealModifier;
        mRippleAlpha = (int) (mBaseRippleAlpha * alphaModifier);
    }

    @Override
    public void draw(Canvas canvas) {
        Component targetView = mPromptOptions.getTargetView();
        if (targetView != null && mPromptOptions.getIconDrawable() == null && mPosition != null) {
            RectFloat rectFloat = new RectFloat(
                    0, 0,
                    mPosition.getPointX() + targetView.getWidth() * Constants.NUM_2,
                    mPosition.getPointY() + targetView.getHeight() * Constants.NUM_2);
            if (rectFloat != null) {
                int layerId = canvas.saveLayer(rectFloat, new Paint());
                drawView(canvas);
                Paint paint = new Paint();
                paint.setColor(Color.TRANSPARENT);
                paint.setBlendMode(BlendMode.SRC);
                canvas.drawPath(mPathIn, paint);
                canvas.restoreToCount(layerId);
            }
        } else {
            drawView(canvas);
        }
    }

    private void drawView(Canvas canvas) {
        final float oldAlpha = mPaint.getAlpha();
        final int oldColor = mPaint.getColor().getValue();
        Color color = new Color(oldColor);
        if (isDrawRipple) {
            if (oldColor == Color.TRANSPARENT.getValue()) {
                mPaint.setColor(Color.WHITE);
            }
            mPaint.setAlpha(1f * mRippleAlpha / Constants.NUM_255);
            canvas.drawCircle(mPosition.getPointX(), mPosition.getPointY(), mRippleRadius, mPaint);
        }
        mPaint.setColor(color);
        mPaint.setAlpha(oldAlpha);
        canvas.drawPath(getPath(), mPaint);
    }

    /**
     * contains
     *
     * @param x x coordinate.
     * @param y y coordinate.
     * @return boolean
     */
    @Override
    public boolean contains(float x, float y) {
        return PromptUtils.isPointInCircle(x, y, mPosition, mRadius);
    }
}
